﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Markup;
using System.Windows.Navigation;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
using DeSleeper.Client.Properties;
using DeSleeper.Client.WakeOnLanService;

namespace DeSleeper.Client.WakeUp
{
    [XmlRoot]
    public class TargetComputer : Freezable, IXmlSerializable
    {
        public static IList<TargetComputer> All
        {
            get
            {
                if (Settings.Default.Targets == null)
                    Settings.Default.Targets = new List<TargetComputer> ();
                return Settings.Default.Targets;
            }
        }

        public TargetComputer()
        {
            Id = "Target/" + Guid.NewGuid();
        }

        #region Id Dependency Property
        /// <summary>
        /// Id Dependency Property.
        /// </summary>
        public static readonly DependencyProperty IdProperty =
            DependencyProperty.Register(
                "Id",
                typeof(string),
                typeof(TargetComputer));

        /// <summary>
        /// Identifier used to identify target to code.
        /// </summary>
        public string Id
        {
            get { return (string)GetValue(IdProperty); }
            set { SetValue(IdProperty, value); }
        }
        #endregion

        #region Description Dependency Property
        /// <summary>
        /// A description of the target used to identify it to the user.
        /// </summary>
        public string Description
        {
            get { return (string) GetValue(DescriptionProperty); }
            set { SetValue(DescriptionProperty, value); }
        }

        /// <summary>
        /// Description Dependency Property.
        /// </summary>
        public static readonly DependencyProperty DescriptionProperty =
            DependencyProperty.Register(
                "Description",
                typeof (string),
                typeof (TargetComputer));
        #endregion

        #region DestinationId Dependency Property
        /// <summary>
        /// Saves the linked destination from the available list.
        /// </summary>
        public string DestinationId
        {
            get { return (string) GetValue(DestinationIdProperty); }
            set { SetValue(DestinationIdProperty, value); }
        }

        /// <summary>
        /// DestinationId Dependency Property.
        /// </summary>
        public static readonly DependencyProperty DestinationIdProperty =
            DependencyProperty.Register(
                "DestinationId",
                typeof(string),
                typeof(TargetComputer),
                new PropertyMetadata(destinationIdChanged));

        private static void destinationIdChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
        {
            var newId = e.NewValue as string;

            var wakeUpDestination = WakeUpDestination.All.FirstOrDefault(destination => string.Equals(destination.Id, newId));
            if (wakeUpDestination != target.GetValue(DestinationProperty))
                target.SetValue(DestinationProperty, wakeUpDestination);
        }

        /// <summary>
        /// Destination Dependency Property.
        /// </summary>
        public static readonly DependencyProperty DestinationProperty =
            DependencyProperty.Register(
                "Destination",
                typeof(WakeUpDestination),
                typeof(TargetComputer), 
                new PropertyMetadata(destinationChanged));

        private static void destinationChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
        {
            var newDestination = e.NewValue as WakeUpDestination;
            if (newDestination != null && !WakeUpDestination.All.Any(destination => string.Equals(destination.Id, newDestination.Id)))
                WakeUpDestination.All.Add(newDestination);

            if (!string.Equals((string)target.GetValue(DestinationIdProperty), newDestination.Id))
                target.SetValue(DestinationIdProperty, newDestination.Id);

            var methodID = target.GetValue(WakeUpMethodIdProperty) as string;
            target.SetValue(WakeUpMethodProperty, newDestination.Methods.FirstOrDefault(method => string.Equals(method.Id, methodID)));
        }

        [XmlIgnore]
        public WakeUpDestination Destination
        {
            get { return (WakeUpDestination)GetValue(DestinationProperty); }
            set { SetValue(DestinationProperty, value); }
        }
        #endregion

        #region WakeUpMethodId Dependency Property

        /// <summary>
        /// 
        /// </summary>
        public string WakeUpMethodId
        {
            get { return (string)GetValue(WakeUpMethodIdProperty); }
            set { SetValue(WakeUpMethodIdProperty, value); }
        }

        /// <summary>
        /// WakeUpMethodId Dependency Property.
        /// </summary>
        public static readonly DependencyProperty WakeUpMethodIdProperty =
            DependencyProperty.Register(
                "WakeUpMethodId",
                typeof(string),
                typeof(TargetComputer), 
                new PropertyMetadata(wakeUpMethodIdChanged));

        private static void wakeUpMethodIdChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
        {
            var newId = e.NewValue as string;

            var currentMethod = target.GetValue(WakeUpMethodProperty) as WakeUpMethod;
            if (currentMethod == null || !string.Equals(newId, currentMethod.Id))
            {
                var wakeUpMethod = findMethod(target, newId);
                target.SetValue(WakeUpMethodProperty, wakeUpMethod);
            }
        }

        private static WakeUpMethod findMethod(DependencyObject target)
        {
            var wakeUpMethodId = target.GetValue(WakeUpMethodIdProperty) as string;
            return findMethod(target, wakeUpMethodId);
        }

        private static WakeUpMethod findMethod(DependencyObject target, string newId)
        {
            var destination = target.GetValue(DestinationProperty) as WakeUpDestination;
            return destination == null ? null : destination.Methods.FirstOrDefault(method => string.Equals(method.Id, newId));
        }

        /// <summary>
        /// WakeUpMethod Dependency Property.
        /// </summary>
        public static readonly DependencyProperty WakeUpMethodProperty =
            DependencyProperty.Register(
                "WakeUpMethod",
                typeof(WakeUpMethod),
                typeof(TargetComputer),
                new PropertyMetadata(null, wakeUpMethodChanged, coerceWakeUpMethod));

        private static object coerceWakeUpMethod(DependencyObject target, object baseValue)
        {
            return baseValue ?? findMethod(target);
        }

        private static void wakeUpMethodChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
        {
            var wakeUpMethod = e.NewValue as WakeUpMethod;
            if (wakeUpMethod != null)
            {
                wakeUpMethod.Target = target as TargetComputer;
                if (!string.Equals((string)target.GetValue(WakeUpMethodIdProperty), wakeUpMethod.Id))
                    target.SetValue(WakeUpMethodIdProperty, wakeUpMethod.Id);
            }
        }

        public WakeUpMethod WakeUpMethod
        {
            get { return (WakeUpMethod) GetValue(WakeUpMethodProperty); }
            set { SetValue(WakeUpMethodProperty, value); } //WakeUpMethodId = value.Id; }
        }
        #endregion


        ///<summary>
        /// Creates a new instance to support cloning.
        ///</summary>
        protected override Freezable CreateInstanceCore()
        {
            return new TargetComputer();
        }

        public void WakeUp()
        {
            Destination.WakeUp(this);
        }

        public void WakeUp(WakeUpServiceClient serviceClient)
        {
            WakeUpMethod.WakeUp(serviceClient);
        }

        #region Implementation of IXmlSerializable

        public XmlSchema GetSchema()
        {
            throw new System.NotImplementedException();
        }

        public void ReadXml(XmlReader reader)
        {
            reader.ReadStartElement();
            
            TargetComputer tempValue;
            using (var subReader = reader.ReadSubtree())
                tempValue = XamlReader.Load(subReader) as TargetComputer;

            if (reader.NodeType == XmlNodeType.Element)
                reader.Read();            // <TargetComputer xmlns="clr-namespace:DeSleeper.Client.WakeUp;assembly=DeSleeper"/>
            else
                reader.ReadEndElement();  // </TargetComputer xmlns="clr-namespace:DeSleeper.Client.WakeUp;assembly=DeSleeper">
            reader.ReadEndElement();  // </TargetComputer>

            if (tempValue == null) return;

            var enumerator = tempValue.GetLocalValueEnumerator();
            while (enumerator.MoveNext())
            {
                var current = enumerator.Current;
                if (current.Value != current.Property.DefaultMetadata.DefaultValue &&
                    current.Property.OwnerType != typeof (XmlAttributeProperties) &&
                    current.Property.OwnerType != typeof (NameScope) &&
                    current.Property.OwnerType != typeof (BaseUriHelper))
                {
                    SetValue(current.Property, current.Value);
                }
            }
        }

        public void WriteXml(XmlWriter writer)
        {
            XamlWriter.Save(this, writer);
        }

        protected override bool ShouldSerializeProperty(DependencyProperty dp)
        {
            if (dp == WakeUpMethodProperty || dp == DestinationProperty) return false;
            return base.ShouldSerializeProperty(dp);
        }

        #endregion
    }
}
